import ArrayCache from "../../../utils/array-cache";
import { ArrayCopy } from "../../../utils/array-utils";
import { MatchFormat, StackList } from "../../../rule/InjectionRule";

export const SUPPLY_STRING = Symbol("STRING");
export const SUPPLY_LIST = Symbol("LIST");
export const SUPPLY_BACK = Symbol("BACK");

export interface CatchBufferInfo { type: "text" | "group"; cache: string; }

export default class CatchBuffer extends ArrayCache<string | number | symbol> {

	public EnterString(): void {
		this.push(SUPPLY_STRING);
	}

	public EnterList(): void {
		this.push(SUPPLY_LIST);
	}

	public Back(): void {
		this.push(SUPPLY_BACK);
	}

	public GetLastIndex(): number {
		const { list, length } = this;
		for (let i = length - 1; i >= 0; i--) {
			if (typeof list[i] !== "string") {
				return i;
			}
		}
		return -1;
	}

	public GetLastCatch(): string {
		const { list } = this;
		const content = list.slice(this.GetLastIndex() + 1, -1).join("");
		return content;
	}

	public Supply(data: MatchFormat): void {
		if (typeof data === "string") {
			this.push(SUPPLY_STRING, ...data, SUPPLY_BACK);
		} else {
			this.EnterList();
			for (const item of data) {
				this.Supply(item);
			}
			this.Back();
		}
	}

	public Format(): MatchFormat | undefined {
		const buffer = new StackList<CatchBufferInfo>();
		for (const char of this.list) {
			if (char === SUPPLY_STRING) {
				buffer.push({ type: "text", cache: "" });
			} else if (char === SUPPLY_LIST) {
				buffer.push({ type: "group", cache: "" });
			} else if (char === SUPPLY_BACK) {
				buffer.back();
			} else {
				if (buffer.select) {
					buffer.selectCheck.info.cache += char as string;
				}
			}
		}
		return CatchBuffer.ParseCatchBuffer(buffer);
	}

	public static ParseCatchBuffer(catchStack: StackList<CatchBufferInfo>): MatchFormat | undefined {
		return catchStack.map((callback, item) => {
			return item.info.type === "text"
				? (
					item.children.length === 0
						? item.info.cache
						: [item.info.cache, item.children.map((...args) => callback(callback, ...args))]
				)
				: item.children.map((...args) => callback(callback, ...args));
		});
	}

	public copy(): CatchBuffer {
		const newArr = new CatchBuffer(this.cache_size);
		newArr._index = this._index;
		newArr._list = ArrayCopy(this._list);
		return newArr;
	}

}
